import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;

/**
 * @author FEIXINAG
 * @date 2024/09/07
 * @description 123网盘代码简单实例
 */
public class Pan123Demo {

    static String requestUrl = "https://open-api.123pan.com";

    //TODO
    static String accessToken = "******";

    /**
     * 根据文件路径生成文件的MD5校验码。
     *
     * @param filePath 文件路径
     * @return MD5校验码的十六进制字符串
     */
    public static String generateMD5(String filePath) {
        try (InputStream is = Files.newInputStream(Paths.get(filePath))) {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[8192];
            int read = 0;

            while ((read = is.read(buffer)) > 0) {
                digest.update(buffer, 0, read);
            }

            byte[] md5sum = digest.digest();
            return toHexString(md5sum);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 将字节数组转换为十六进制字符串。
     *
     * @param bytes 字节数组
     * @return 十六进制形式的字符串
     */
    public static String toHexString(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }

    /**
     * 创建文件
     * @param parentFileID *父目录id，上传到根目录时填写 0
     * @param filename 文件名要小于255个字符且不能包含以下任何字符："\/:*?|><。（注：不能重名）
     * @param etag 文件md5
     * @param size 文件大小，单位为 byte 字节
     * @return JsonResult 包含创建文件的结果：<br/>
     * fileID-文件ID。当123云盘已有该文件,则会发生秒传。此时会将文件ID字段返回。唯一<br/>
     * preuploadID-预上传ID(如果 reuse 为 true 时,该字段不存在)<br/>
     * reuse-是否秒传，返回true时表示文件已上传成功<br/>
     * sliceSize-分片大小，必须按此大小生成文件分片再上传<br/>
     */
    public static JsonNode createFile(Integer parentFileID, String filename, String etag, long size) {
        JsonNode result;
        System.out.println("【创建文件】");
        try {
            HttpClient httpClient = HttpClients.createDefault();
            HttpPost httpPost = new HttpPost(requestUrl + "/upload/v1/file/create");

            httpPost.setHeader("Authorization", accessToken);
            httpPost.setHeader("Platform", "open_platform");
            httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");

            ObjectMapper mapper = new ObjectMapper();
            ObjectNode jsonBody = mapper.createObjectNode();
            jsonBody.put("parentFileID", parentFileID);
            jsonBody.put("filename", filename);
            jsonBody.put("etag", etag);
            jsonBody.put("size", size);

            StringEntity entity = new StringEntity(jsonBody.toString(),
                    ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8));
            httpPost.setEntity(entity);

            HttpResponse response = httpClient.execute(httpPost);
            HttpEntity responseEntity = response.getEntity();

            if (responseEntity != null) {
                result = new ObjectMapper().readTree(EntityUtils.toString(responseEntity));
                System.out.println(result);
                return result;
            }
        } catch (Exception e) {
            //TODO//自行处理异常
        }
        return null;
    }

    /**
     * 获取上传地址
     * @param preuploadID 预上传ID
     * @param sliceNo 分片序号
     * @return 包含上传地址的JsonResult对象
     */
    private static String getUploadUrl(String preuploadID, int sliceNo) {
        System.out.println("【获取上传地址】");
        System.out.println("preuploadID："+preuploadID);
        System.out.println("sliceNo："+sliceNo);
        try {
            // 创建HttpClient实例
            HttpClient httpClient = HttpClients.createDefault();

            // 创建HttpPost对象
            HttpPost httpPost = new HttpPost(requestUrl + "/upload/v1/file/get_upload_url");

            // 设置请求头
            httpPost.setHeader("Authorization", accessToken);
            httpPost.setHeader("Platform", "open_platform");
            httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");

            // 创建ObjectMapper实例
            ObjectMapper mapper = new ObjectMapper();
            ObjectNode jsonBody = mapper.createObjectNode();
            jsonBody.put("preuploadID", preuploadID);
            jsonBody.put("sliceNo", sliceNo);

            // 创建请求体
            StringEntity entity = new StringEntity(jsonBody.toString(),
                    ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8));
            httpPost.setEntity(entity);

            // 执行请求
            HttpResponse response = httpClient.execute(httpPost);

            // 获取响应实体
            HttpEntity responseEntity = response.getEntity();

            if (responseEntity != null) {
                JsonNode resultData = new ObjectMapper().readTree(EntityUtils.toString(responseEntity));

                if(resultData.get("code").asInt() == 0){
                    String url = resultData.path("data").path("presignedURL").asText();
                    System.out.println(url);
                    return url;
                }else{
                    return null;
                }
            }
        } catch (Exception e) {
            //TODO
        }
        return null;
    }

    /**
     * 完整上传文件
     * @param dirId 文件夹Id
     * @param filePath 文件本地全路径
     * @param fileName 文件名
     * @param fileSize 文件大小（字节）
     * @return 上传结果
     * @throws IOException 输入输出异常
     */
    public static void upload(Integer dirId, String filePath, String fileName, long fileSize){
        System.out.println("######【完整上传文件】######");

        String md5 = generateMD5(filePath);
        File file = new File(filePath);

        // Step 1: Create file
        JsonNode createFileResponse = createFile(dirId, fileName, md5, fileSize);

        System.out.println("【上传文件】创建文件结果");
        System.out.println(createFileResponse);

        // 创建文件成功
        if(createFileResponse.get("code").asInt() == 0){
            //非直接上传//网盘中不曾有过这个文件
            if(!createFileResponse.get("data").path("reuse").asBoolean()){
                String preuploadID = createFileResponse.get("data").path("preuploadID").asText();
                long sliceSize = createFileResponse.get("data").path("sliceSize").asLong();

                //总分片数
                int totalSlices = 1;
                if(fileSize > sliceSize){
                    totalSlices = (int) Math.ceil((double) fileSize / sliceSize);
                }

                System.out.println("【上传文件】总分片数："+totalSlices);

                try (FileInputStream fis = new FileInputStream(file)) {
                    for (int sliceNo = 1; sliceNo <= totalSlices; sliceNo++) {
                        String uploadUrl = getUploadUrl(preuploadID, sliceNo);

                        System.out.println("【上传文件】上传分片之："+sliceNo);
                        System.out.println("【上传文件】上传该分片路径："+uploadUrl);

                        long start = (sliceNo - 1) * sliceSize;
                        long end = Math.min(sliceNo * sliceSize, fileSize);
                        try (InputStream sliceStream = new FileInputStream(file)) {
                            sliceStream.skip(start);
                            byte[] buffer = new byte[(int) (end - start)];
                            sliceStream.read(buffer);
                            //上传分片
                            uploadSlice(uploadUrl, buffer);
                        }
                    }

                    //列举已上传分片
                    if (fileSize > sliceSize) {
                        JsonNode parts = listUploadParts(preuploadID);
                        System.out.println("已经上传的分片列表：");
                        System.out.println(parts);
                        //如果需要对比本地，这里就可以继续进行判断了//TODO
                    }

                    //告诉123网盘已经上传完毕，进行合并
                    JsonNode uploadCompleteResponse = uploadComplete(preuploadID);

                    if(uploadCompleteResponse.get("completed").asBoolean()){
                        System.out.println("【上传完成】");
                        System.out.println(uploadCompleteResponse);

                        //是否需要异步查询上传结果
                        //return uploadCompleteResponse;
                        if(uploadCompleteResponse.get("async").asBoolean()){
                            System.out.println("进行异步上传轮询");

                            boolean flag = true;

                            int times = 0;
                            while (flag){
                                //TODO
                                listUploadParts(preuploadID);
                                flag = !uploadAsyncResult(preuploadID) && ++times < 10;
                                try {
                                    Thread.sleep(2000);
                                } catch (InterruptedException e) {
                                    throw new RuntimeException(e);
                                }
                            }

                        }
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }else{
                System.out.println("★★★秒传成功（文件已从回收站回收，或者回收站已清空也给你恢复，等等）★★★");
            }
        }
    }

    /**
     * 上传分片
     * @param uploadUrl 文件上传的URL
     * @param data 文件的二进制数据
     */
    public static void uploadSlice(String uploadUrl, byte[] data) {
        System.out.println("【上传分片】上传地址:"+uploadUrl);
        System.out.println("【上传分片】分片大小:"+data.length);
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 创建HttpPost对象
            HttpPut httpPut = new HttpPut(uploadUrl);
            // 创建ByteArrayEntity并设置数据
            HttpEntity entity = new ByteArrayEntity(data, ContentType.APPLICATION_OCTET_STREAM);
            // 设置请求的实体
            httpPut.setEntity(entity);

            // 执行请求
            HttpResponse response = httpClient.execute(httpPut);
            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode >= 200 && statusCode < 300) {
                // 检查响应内容
                String responseBody = EntityUtils.toString(response.getEntity());
                // 这里可以添加额外的逻辑来处理响应内容
                System.out.println("上传成功！！！！");
            } else {
                System.err.println("上传文件失败，状态码：" + statusCode);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 列举已上传分片
     * @param preuploadID 预备上传ID
     * @return 分片信息列表：<br/>
     * parts-分片列表<br/>
     * parts[*].partNumber-分片编号<br/>
     * parts[*].size-分片大小<br/>
     * parts[*].etag-分片md5<br/>
     */
    public static JsonNode listUploadParts(String preuploadID) {
        JsonNode result;
        System.out.println("【列举已上传分片】");
        try {
            // 创建HttpClient实例
            HttpClient httpClient = HttpClients.createDefault();

            // 创建HttpPost对象
            HttpPost httpPost = new HttpPost(requestUrl + "/upload/v1/file/list_upload_parts");

            // 设置请求头
            httpPost.setHeader("Authorization", accessToken);
            httpPost.setHeader("Platform", "open_platform");
            httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");

            // 创建ObjectMapper实例
            ObjectMapper mapper = new ObjectMapper();
            ObjectNode jsonBody = mapper.createObjectNode();
            jsonBody.put("preuploadID", preuploadID);

            // 创建请求体
            StringEntity entity = new StringEntity(jsonBody.toString(),
                    ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8));
            httpPost.setEntity(entity);

            // 执行请求
            HttpResponse response = httpClient.execute(httpPost);

            // 获取响应实体
            HttpEntity responseEntity = response.getEntity();

            if (responseEntity != null) {
                result = new ObjectMapper().readTree(EntityUtils.toString(responseEntity));
                System.out.println(result);
                return result;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.err.println("列举分片失败");
        return null;
    }

    /**
     * 上传完毕（文件上传完成接口调用）
     * @param preuploadID 预上传ID
     * @return JsonResult 包含上传结果的JsonResult对象
     */
    public static JsonNode uploadComplete(String preuploadID) {
        try {
            // 创建HttpClient实例
            HttpClient httpClient = HttpClients.createDefault();

            // 创建HttpPost对象
            HttpPost httpPost = new HttpPost(requestUrl + "/upload/v1/file/upload_complete");

            // 设置请求头
            httpPost.setHeader("Authorization", accessToken);
            httpPost.setHeader("Platform", "open_platform");
            httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");

            // 创建ObjectMapper实例
            ObjectMapper mapper = new ObjectMapper();
            ObjectNode jsonBody = mapper.createObjectNode();
            jsonBody.put("preuploadID", preuploadID);

            // 创建请求体
            StringEntity entity = new StringEntity(jsonBody.toString(),
                    ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8));
            httpPost.setEntity(entity);

            // 执行请求
            HttpResponse response = httpClient.execute(httpPost);

            // 获取响应实体
            HttpEntity responseEntity = response.getEntity();

            if (responseEntity != null) {
                String responseString = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
                JsonNode resultString = mapper.readTree(responseString);

                JsonNode resultData = resultString.get("data");

                ObjectNode result = mapper.createObjectNode();
                if (resultData.get("completed").asBoolean()) {
                    result.put("completed", resultData.get("completed").asBoolean());
                    if (resultData.get("completed").asBoolean()) {
                        result.put("fileID", resultData.get("fileID").asInt());
                        result.put("async", resultData.get("async").asBoolean());
                    }
                    return result;
                }else{
                    return resultData;
                }
            } else {
                System.err.println("文件上传完成接口调用未返回响应体");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 异步轮询获取上传结果
     * @param preuploadID 预上传ID
     * @return 异步上传结果
     */
    public static boolean uploadAsyncResult(String preuploadID) {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 创建HttpPost对象
            HttpPost httpPost = new HttpPost(requestUrl + "/upload/v1/file/upload_async_result");

            // 设置请求头
            httpPost.setHeader("Authorization", accessToken);
            httpPost.setHeader("Platform", "open_platform");
            httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");

            // 创建ObjectMapper实例
            ObjectMapper mapper = new ObjectMapper();
            ObjectNode jsonBody = mapper.createObjectNode();
            jsonBody.put("preuploadID", preuploadID);

            // 创建StringEntity对象
            StringEntity entity = new StringEntity(jsonBody.toString(),
                    ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8));
            httpPost.setEntity(entity);

            // 执行请求
            HttpResponse response = httpClient.execute(httpPost);

            // 获取响应实体
            HttpEntity responseEntity = response.getEntity();

            // 打印响应内容
            if (responseEntity != null) {
                String responseString = EntityUtils.toString(responseEntity);
                ObjectMapper mapper1 = new ObjectMapper();
                JsonNode rootNode = mapper1.readTree(responseString);
                boolean completed = rootNode.path("completed").asBoolean();
                JsonNode fileIDNode = rootNode.path("fileID");

                if (completed) {
                    // 上传完成
                    System.err.println("异步轮询上传完成");
                    return true;
                } else {
                    // 上传未完成，需要轮询
                    System.err.println("异步轮询继续上传");
                    return false;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /*public static void main(String[] args){
        // 生成MD5
        String path = "B:\\feixiang.txt";
        System.out.println(generateMD5(path));
    }*/

    /*public static void main(String[] args) throws IOException {
        // 创建文件
        String path = "B:\\feixiang.txt";
        createFile(0,"飞翔.txt",generateMD5(path),Files.size(Paths.get(path)));
    }*/

    /*public static void main(String[] args) {
        // 获取上传地址（一个分片生成一个上传地址）
        String preuploadID = "WvjyUgonimrlBq22JosMA3CIHeoMSmZuTI3A34x1JI3gHhC6ToFyFojmilOlU0FlDljLCIilAcjpTli6iMpmqMW/odBMVhZxilOlUgonimrOAcjwWli6CwY2CvOlSz32imrlRgePRgRZCmiNBIYvDIQOCdQxBHxOAzGZB4GpQIQvRpBvRgfnBIe1D4GnBmjpCIbZCwiwD4FyilOlS5CLVHi6BcOlVhbpS4FlDmePCwU1Ddn1AcjxS4FlDli1BdJPC4BvClxxC4i0AIGnBwJLQ4FPCHyoDIZnCde1CdUzBzGXSz9MRvynQlxyDIVlDdJxD4jnA5jLS0YzilOlUzxlDmJPDIJOCIe2CwY0CwY2BwaPDdV9";
        int sliceSize = 16777216;
        getUploadUrl(preuploadID, sliceSize);
    }*/

    public static void main(String[] args) throws IOException {
        //完整上传
        String path = "B:\\feixiang.zip";
        upload(0, path, "feixiang.zip", Files.size(Paths.get(path)));
    }
}
